package com.kedacom.ctsp.authz.oauth2.client.controller;

import com.alibaba.fastjson.JSON;
import com.kedacom.ctsp.authz.entity.Authentication;
import com.kedacom.ctsp.authz.exception.UnauthorizedException;
import com.kedacom.ctsp.authz.oauth2.client.OAuth2ClientProperties;
import com.kedacom.ctsp.authz.oauth2.client.service.OAuth2ResourceService;
import com.kedacom.ctsp.authz.oauth2.client.service.OAuth2Service;
import com.kedacom.ctsp.authz.oauth2.client.vo.AuthenticationVO;
import com.kedacom.ctsp.authz.oauth2.core.ErrorType;
import com.kedacom.ctsp.authz.oauth2.core.OAuth2Authentication;
import com.kedacom.ctsp.authz.oauth2.core.OAuth2Exception;
import com.kedacom.ctsp.authz.oauth2.core.vo.AccessToken;
import com.kedacom.ctsp.authz.oauth2.core.vo.UserQueryParam;
import com.kedacom.ctsp.authz.security.provider.AuthUserDetails;
import com.kedacom.ctsp.authz.security.provider.UserBean;
import com.kedacom.ctsp.authz.security.util.UserBeanUtil;
import com.kedacom.ctsp.lang.mapper.BeanMapper;
import com.kedacom.ctsp.logging.AccessLogger;
import com.kedacom.ctsp.web.controller.message.ResponseMessage;
import com.kedacom.ctsp.web.controller.util.Constant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.stereotype.Controller;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.net.URLDecoder;

import static com.kedacom.ctsp.authz.oauth2.client.common.constant.I18nConstant.ACCESSLOG_ACTION_ME;
import static com.kedacom.ctsp.authz.oauth2.client.common.constant.I18nConstant.ACCESSLOG_MODULE_OAUTH2;
import static com.kedacom.ctsp.web.controller.message.ResponseMessage.ok;


/**
 * Created by xuwei on 2017/3/3.
 */
@Controller
@RequestMapping("/oauth2")
@Slf4j
@AccessLogger(ACCESSLOG_MODULE_OAUTH2)
public class OAuth2Controller {

    @Autowired
    private OAuth2ClientProperties clientProperties;
    @Autowired
    private OAuth2Service oauthService;
    @Autowired
    private OAuth2ResourceService resourceService;
    @Autowired
    private LogoutHandler logoutHandler;

    /**
     * 向SSO站点申请访问令牌
     *
     * @param response
     * @return
     */
    @RequestMapping("/authorize")
    public ModelAndView authorize(HttpServletRequest request, HttpServletResponse response) {
        try {
            log.info(String.format("========步骤是:%d_方法是:%s_功能是:%s,参数是:%s", 1, "OAuth2Controller.authorize", "向SSO站点申请访问令牌", ""));
            String applyForTokenUri = oauthService.authorize(request, response);
            return new ModelAndView(new RedirectView(applyForTokenUri));
        } catch (Exception e) {
            log.error("applyfortoken is failed:", e);
            return null;
        }
    }

    /**
     * 授权码模式回调接口
     *
     * @param request
     * @param response
     * @param state
     * @param code
     * @return
     * @throws Exception
     */
    @RequestMapping("callback")
    public ModelAndView authorizationCodeCallback(HttpServletRequest request, HttpServletResponse response, String state, String code, String return_uri) throws Exception {
        Boolean stateIsValid = oauthService.checkState(request, response, state);
        if (stateIsValid) {
            log.info(String.format("========步骤是:%d_方法是:%s_功能是:%s,参数是:%s", 5, "OAuth2Controller.authorizationCodeCallback", "授权码模式回调接口", "code=" + return_uri + "&return_uri" + return_uri));
            AccessToken accessToken = oauthService.applyForAuthorizationCode(code);
            log.info(String.format("========步骤是:%d_方法是:%s_功能是:%s,参数是:%s", 7, "OAuth2Controller.authorizationCodeCallback", "授权码模式回调接口", JSON.toJSONString(accessToken)));
            if (null != accessToken) {
                setCurrentAuthentication(accessToken);
                HttpSession session = request.getSession(true);
                session.setAttribute("SPRING_SECURITY_CONTEXT", SecurityContextHolder.getContext());
                if (StringUtils.isNotEmpty(return_uri)) {
                    return new ModelAndView(new RedirectView(URLDecoder.decode(return_uri)));
                }
                return new ModelAndView(new RedirectView("/"));
            } else {
                throw new OAuth2Exception(ErrorType.INVALID_TOKEN);
            }
        } else {
            throw new OAuth2Exception(ErrorType.INVALID_STATE);
        }
    }

    /**
     * 密码模式
     *
     * @param request
     * @return
     * @throws Exception
     */
    @PostMapping("password")
    @ResponseBody
    public ResponseMessage<AccessToken> authorizationPasswordCallback(HttpServletRequest request) {

        UserBean authUser = UserBeanUtil.createLoginUserBean(request);
        if (authUser != null) {
            Assert.notNull(authUser.getUsername(), "username can not be null");
            Assert.notNull(authUser.getPassword(), "password can not be null");
        }
        AccessToken accessToken = oauthService.applyForPassword(authUser.getUsername(), authUser.getPassword());
        if (null != accessToken) {
            setCurrentAuthentication(accessToken);
            return ResponseMessage.ok(accessToken);
        }
        return ResponseMessage.ok();
    }

    /**
     * 根据accessToken信息在当前系统登录
     *
     * @param accessToken
     */
    private void setCurrentAuthentication(AccessToken accessToken) {
        OAuth2Authentication oAuth2Authentication = oauthService.getAuthenticationByAccessToken(accessToken);
        oAuth2Authentication.setAccessToken(accessToken);

        AuthUserDetails principal = new AuthUserDetails(oAuth2Authentication);
        PreAuthenticatedAuthenticationToken auth =
                new PreAuthenticatedAuthenticationToken(principal, accessToken, principal.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(auth);
        log.info(String.format("========步骤是:%d_方法是:%s_功能是:%s,参数是:%s", 8, "OAuth2Controller.setCurrentAuthentication", "根据accessToken信息在当前系统登录", JSON.toJSONString(auth)));
    }

    /**
     * 退出登录
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @RequestMapping("logout")
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        AccessToken token = getAccessToken();
        if (token == null) {
            return "redirect:/";
        }
        logoutHandler.logout(request, response, SecurityContextHolder.getContext().getAuthentication());
        StringBuffer fullLogoutUri = new StringBuffer().append(clientProperties.getServerUri() + clientProperties.getSsoLogoutUri());
        fullLogoutUri.append("?")
                .append("access_token").append("=").append(token.getAccessToken())
                .append("&client_id").append("=").append(clientProperties.getClientId());
        return "redirect:" + fullLogoutUri;
    }

    private AccessToken getAccessToken() {
        OAuth2Authentication authentication = (OAuth2Authentication) Authentication.current().orElseThrow(UnauthorizedException::new);
        return authentication.getAccessToken();
    }

    @GetMapping("/me")
    @AccessLogger(ACCESSLOG_ACTION_ME)
    public ResponseMessage<AuthenticationVO> me() {
        OAuth2Authentication auth = (OAuth2Authentication) Authentication.current().orElseThrow(UnauthorizedException::new);
        UserQueryParam queryParam = buildParam(auth);
        AuthenticationVO vo = buildVO(auth, queryParam);
        return ok(vo);
    }

    private AuthenticationVO buildVO(OAuth2Authentication auth, UserQueryParam queryParam) {
        AuthenticationVO vo = BeanMapper.deepMap(auth, AuthenticationVO.class);
        vo.setResources(resourceService.queryResourceAsTreeByUser(null, queryParam).getResult());
        return vo;
    }

    private UserQueryParam buildParam(OAuth2Authentication auth) {
        UserQueryParam queryParam = new UserQueryParam(auth.getAccessToken().getAccessToken());
        queryParam.setClientId(clientProperties.getClientId());
        queryParam.setClientSecret(clientProperties.getClientSecret());
        return queryParam;
    }

}
